package gnmi

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/golang/protobuf/proto"
	"github.com/kataras/golog"
	ocGnmi "github.com/openconfig/gnmi/proto/gnmi"
	"google.golang.org/grpc"
	"sdn-executor/utils/erros"
	"strconv"
	"strings"
	"time"
)

type GNMIClient struct {
	device string
	Port   int16
	User   string
	Pwd    string

	conn   *grpc.ClientConn
	client ocGnmi.GNMIClient
}

func NewGNMIClient(device string, port int16, user string, pwd string) (*GNMIClient, *erros.Error) {

	opts := ClientCredentials(user, pwd)
	target := device + ":" + strconv.Itoa(int(port))
	conn, err := grpc.Dial(target, opts...)
	if err != nil {
		golog.Errorf("Dialing failed. target: %s, err: %+v", target, err)
		return nil, erros.New(erros.CodeOK, "connect failed")
	}

	client := ocGnmi.NewGNMIClient(conn)

	return &GNMIClient{
		device: device,
		Port:   port,
		User:   user,
		Pwd:    pwd,
		conn:   conn,
		client: client,
	}, nil
}

func parseModelData(s *string) (*ocGnmi.ModelData, error) {
	pbModelData := new(ocGnmi.ModelData)
	modelDataVars := strings.Split(*s, ",")
	if len(modelDataVars) != 3 {
		return pbModelData, fmt.Errorf("Unable to parse string")
	}
	pbModelData = &ocGnmi.ModelData{
		Name:         modelDataVars[0],
		Organization: modelDataVars[1],
		Version:      modelDataVars[2],
	}
	return pbModelData, nil
}

func (c *GNMIClient) Capabilities() (*ocGnmi.CapabilityResponse, *erros.Error) {

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	capResponse, err := c.client.Capabilities(ctx, &ocGnmi.CapabilityRequest{})
	if err != nil {
		golog.Errorf("error in getting capabilities. err: %+v", err)
		return nil, erros.New(erros.CodeInternalError, err.Error())
	}

	golog.Infof("CapabilitiesResponse: %s", proto.MarshalTextString(capResponse))
	return capResponse, nil
}

func (c *GNMIClient) Get(xpaths []string, pbModelDatas []string) (*ocGnmi.GetResponse, *erros.Error) {

	// 编码格式
	encodingName := "JSON_IETF"
	encoding, ok := ocGnmi.Encoding_value[encodingName]
	if !ok {
		var gnmiEncodingList []string
		for _, name := range ocGnmi.Encoding_name {
			gnmiEncodingList = append(gnmiEncodingList, name)
		}
		golog.Errorf("Supported encodings: %s", strings.Join(gnmiEncodingList, ", "))
	}

	// 获取的内容
	dataType := ocGnmi.GetRequest_ALL

	var pbPathList []*ocGnmi.Path
	var pbModelDataList []*ocGnmi.ModelData
	for _, xPath := range xpaths {
		gnmiPath, err := ToGNMIPath(xPath)
		if err != nil {
			golog.Errorf("error in parsing xpath to gnmi path. xpath: %s", xPath)
		}
		pbPathList = append(pbPathList, gnmiPath)
	}

	for _, textPbModelData := range pbModelDatas {
		gnmiModelData, err := parseModelData(&textPbModelData)
		if err == nil {
			pbModelDataList = append(pbModelDataList, gnmiModelData)
		}
	}

	prefix := ""
	var pbPrefixPath = &ocGnmi.Path{Origin: prefix}

	getRequest := &ocGnmi.GetRequest{
		Encoding:  ocGnmi.Encoding(encoding),
		Path:      pbPathList,
		UseModels: pbModelDataList,
		Prefix:    pbPrefixPath,
		Type:      dataType,
	}
	golog.Infof("== GetRequest: %s", proto.MarshalTextString(getRequest))

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// 发送请求
	getResponse, err := c.client.Get(ctx, getRequest)
	if err != nil {
		golog.Errorf("Get failed. err: %+v", err)
	}

	golog.Infof("== GetRequest: %s", proto.MarshalTextString(getResponse))
	return getResponse, nil
}

func (c *GNMIClient) Update(xpath string, config any) (*ocGnmi.SetResponse, *erros.Error) {

	updates := buildGnmiUpdateList(xpath, config)

	prefix := ""
	var pbPrefixPath = &ocGnmi.Path{Origin: prefix}

	setRequest := &ocGnmi.SetRequest{
		Update: updates,
		Prefix: pbPrefixPath,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	setResponse, err := c.client.Set(ctx, setRequest)
	if err != nil {
		golog.Errorf("Set failed. err: %+v", err)
	}

	return setResponse, nil
}

func (c *GNMIClient) Replace(xpath string, config any) (*ocGnmi.SetResponse, *erros.Error) {

	updates := buildGnmiUpdateList(xpath, config)

	prefix := ""
	var pbPrefixPath = &ocGnmi.Path{Origin: prefix}

	setRequest := &ocGnmi.SetRequest{
		Replace: updates,
		Prefix:  pbPrefixPath,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	setResponse, err := c.client.Set(ctx, setRequest)
	if err != nil {
		golog.Errorf("Set failed.err: %+v", err)
	}

	return setResponse, nil
}

func (c *GNMIClient) Delete(xpath string) (any, *erros.Error) {

	var deleteList []*ocGnmi.Path
	pbPath, _ := ToGNMIPath(xpath)

	deleteList = append(deleteList, pbPath)

	prefix := ""
	var pbPrefixPath = &ocGnmi.Path{Origin: prefix}

	setRequest := &ocGnmi.SetRequest{
		Delete: deleteList,
		Prefix: pbPrefixPath,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	setResponse, err := c.client.Set(ctx, setRequest)
	if err != nil {
		golog.Errorf("Set failed. err: %+v", err)
	}

	return setResponse, nil
}

func buildGnmiUpdateList(xpath string, content any) []*ocGnmi.Update {
	var pbUpdateList []*ocGnmi.Update

	pbPath, _ := ToGNMIPath(xpath)

	var pbVal *ocGnmi.TypedValue
	jsonConfig, _ := json.Marshal(content)
	pbVal = &ocGnmi.TypedValue{
		Value: &ocGnmi.TypedValue_JsonIetfVal{
			JsonIetfVal: jsonConfig,
		},
	}

	pbUpdateList = append(pbUpdateList, &ocGnmi.Update{Path: pbPath, Val: pbVal})
	return pbUpdateList
}

func (c *GNMIClient) Close() {
	if c != nil && c.conn != nil {
		c.conn.Close()
	}
}
